package game.graph;

import excepciones.FinDeJuegoException;
import excepciones.JuegoGanadoException;
import excepciones.TrampaAlcanzadaException;
import game.model.Componente;
import game.model.Juego;
import game.model.Orientable;
import game.model.Origen;
import game.model.Rayo;
import game.model.Tablero;
import gui.BoardPanel;
import gui.BoardPanelListener;
import gui.ImageUtils;

import java.awt.Color;
import java.awt.Image;
import java.awt.Point;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Set;

import javax.swing.JFrame;

import biblioes.ArchivoPuntaje;
import biblioes.Archivos;

/**
 * Panel que contiene al tablero dibujado en pantalla.
 * Carga todas las imagenes del directorio para su uso durante el juego.
 * Se encarga de dos funciones principales, la primera es la de transferir la 
 * informacion dejada por metodos de back end en el tablero y trasladarla en 
 * un formato amigable a la pantalla.
 * Y la segunda la de lanzar los mensajes de informacion al usuario. Tambien
 * atrapa las excepciones lanzadas por el back end para saber cuando mandar estos
 * mensajes, con que informacion, y como continuar el juego.
 *
 */

public class TableroGrafico extends BoardPanel implements BoardPanelListener {

	private static final long serialVersionUID = 1L;
	
	private Juego juego;
	private JFrame frame;
	
	private HashMap<Integer, Image> imagenes; 
	
	/**
	 * Constructor de la clase, levanta todas las imagenes del directorio y las 
	 * almacena para su posterior y continuo uso.
	 * 
	 * @param juego juego con toda la informacion del mismo.
	 * @param frame referencia al frame que almacena este panel. Es necesario a la hora
	 * de cerrar la ventana.
	 * 
	 */
	
	public TableroGrafico(Juego juego, JFrame frame) {
		super(juego.getTablero().getDimensiones().x, juego.getTablero().getDimensiones().y, 30);
		this.juego = juego;
		this.frame = frame;

		try {
			imagenes = new HashMap<Integer, Image>();
			imagenes.put(1, ImageUtils.loadImage("resources/source.png"));
			imagenes.put(2, ImageUtils.loadImage("resources/target.png"));
			imagenes.put(3, ImageUtils.loadImage("resources/simple-mirror.png"));
			imagenes.put(4, ImageUtils.loadImage("resources/double-mirror.png"));
			imagenes.put(5, ImageUtils.loadImage("resources/split-mirror.png"));
			imagenes.put(6, ImageUtils.loadImage("resources/wall.png"));
			imagenes.put(7, ImageUtils.loadImage("resources/trap.png"));
			imagenes.put(8, ImageUtils.loadImage("resources/laser.png"));
			imagenes.put(9, ImageUtils.loadImage("resources/half-laser.png"));
			imagenes.put(10, ImageUtils.rotateImage(ImageUtils.loadImage("resources/half-laser.png"),2));
		} catch (Exception e) {
			String mensaje = "<html>Problemas al cargar las imagenes<br>" +
			 				 "No se puede iniciar el juego</html>";
			new MensajeFrame(mensaje, frame);
		}
	}
	
	/**
	 * En funcion del trabajo del back end, se encarga de transferir esa informacion
	 * a la pantalla para hacerlo visible al usuario
	 * 
	 * @param tablero tablero con la informacion a dibujar.
	 * 
	 */
	
	public void dibujar(Tablero tablero) {
		dibujarRayos(tablero);
		for(int i = 0; i < tablero.getDimensiones().x; i++) {
			for(int j = 0; j < tablero.getDimensiones().y; j++) {
				this.clearImage(i,j);
				Set<Rayo> actuales = tablero.getCelda(new Point(i ,j)).getRayos();
				for(Rayo actual: actuales) {
					Image imagen = imagenes.get(actual.getImagen());
					imagen = ImageUtils.rotateImage(imagen, actual.getOrientacion());
					imagen = ImageUtils.replaceColor(imagen, new Color(0,0,255), actual.getColor());
					this.appendImage(i,j,imagen);
				}
				Componente actual = tablero.getCelda(new Point(i,j)).getComponente();
				Image imagen = imagenes.get(actual.getImagen());
				if(imagen != null) {
					if(actual instanceof Orientable) {
						imagen = ImageUtils.rotateImage(imagen, ((Orientable)actual).getOrientacion());
					} else if(actual instanceof Origen) {
						imagen = ImageUtils.rotateImage(imagen, ((Origen)actual).getOrientacion());
					}
					imagen = ImageUtils.replaceColor(imagen, new Color(0,0,255), actual.getColor());
					this.appendImage(i,j,imagen);
				}
			}
		}
		this.repaint();
		manejaFin(tablero);
		tablero.limpiaRayo();
	}
	
	/**
	 * Al recibir alguna excepcion, deriva en un mensaje al usuario dicha informacion
	 * y continua el juego en el camino adecuado.
	 * 
	 * @param tablero tablero con la informacion para verificar si el juego termina o no.
	 * 
	 */
	
	public void manejaFin(Tablero tablero) {
		try {
			tablero.verificaFin();
		} catch(JuegoGanadoException e) {
			try {
				ArchivoPuntaje.guardarPuntajes(juego.verNivel(), juego.verJugador(), tablero.totalPuntos());
			} catch (IOException e2) {
				String mensaje = "<html>Problema al guardar puntaje<br>" +
				 				 "Su resultado no sera recordado</html>";
				new MensajeFrame(mensaje, frame);
			}
			String mensaje = "Gano el nivel con " + tablero.totalPuntos() + " puntos. " +
							 "Felicitaciones";
			try {
				File siguiente = juego.siguienteNivel();
				if(siguiente == null) {
					new MensajeFrame(mensaje);
					throw new FinDeJuegoException();
				}
				Archivos.cargarArchivo(juego, siguiente);
			} catch(FinDeJuegoException e1) {
				mensaje = "<html>No quedan mas niveles por jugar" +
				  		  "<br>Ha ganado el juego completo</html>";
				new MensajeFrame(mensaje, frame);
				return;
			} catch (IOException e1) {
				mensaje = "<html>No hay archivos de nivel" +
						  "<br>o su formato no es adecuado</html>";
				new MensajeFrame(mensaje, frame);
				return;
			} catch (IllegalArgumentException e1) {
				mensaje = "<html>El archivo de nivel contiene" +
						  "<br>valores no adecuados.</html>";
				new MensajeFrame(mensaje, frame);
				return;
			}
			new MensajeFrame(mensaje, frame, juego).setAlwaysOnTop(true);
		} catch(TrampaAlcanzadaException e) {
			String mensaje = "<html>Nivel perdido<br>" +
							 "Puede comenzar un nuevo juego</html>";
			new MensajeFrame(mensaje, frame);
		}
	}
	
	/**
	 * Dibuja los rayos en el tablero de forma recursiva.
	 * 
	 * @param tablero tablero con los rayos a dibujar.
	 */
	
	public void dibujarRayos(Tablero tablero){
		for(Point puntoOrigen : tablero.getOrigenes()){
			Origen origen = (Origen)tablero.getComponente(puntoOrigen);
			Rayo rayo = new Rayo(origen.getColor(), origen.getOrientacion());
			Point suma = rayo.getPoint(rayo.getOrientacion());
			tablero.dibujar(new Point(suma.x+puntoOrigen.x,suma.y+puntoOrigen.y), rayo);
		}
	}

	/**
	 * Metodo de listener de mouse, lanzado al hacer click sobre una celda.
	 * Llama al metodo de tablero que rota una pieza y es la que valida el 
	 * movimiento.
	 * 
	 * @param row fila sobre la que se hizo click.
	 * @param column columna sobre la que se hizo click.
	 * 
	 */
	
	public void cellClicked(int row, int column) {
		juego.getTablero().rotarPieza(row, column);
		this.dibujar(juego.getTablero());
	}

	/**
	 * Metodo de listener de mouse, lanzado al mover una celda y dejarla sobre otra.
	 * Llama al metodo de tablero que mueve dos piezas y es la que valida el 
	 * movimiento.
	 * 
	 * @param sourceRow fila origen del movimiento.
	 * @param sourceColumn columna origen del movimiento.
	 * @param targetRow fila destino del movimiento.
	 * @param targetColumn columna destino del movimiento.
	 * 
	 */
	
	public void cellDragged(int sourceRow, int sourceColumn, int targetRow,	int targetColumn) {
		juego.getTablero().MoverPiezas(sourceRow, sourceColumn, targetRow, targetColumn);
		this.dibujar(juego.getTablero());
	}
	
	/**
	 * Retorna una referencia al juego.
	 * 
	 * @return Juego juego que se esta corriendo.
	 * 
	 */
	
	public Juego verJuego() {
		return juego;
	}
	
}
